const shellRE = /language-(shellscript|shell|bash|sh|zsh)/
const ignoredNodes = [".vp-copy-ignore", ".diff.remove"].join(", ")

let hasRegisteredCopyListener = false

export function useCopyCode() {
  if (hasRegisteredCopyListener) return
  hasRegisteredCopyListener = true

  const timeoutIdMap: WeakMap<HTMLElement, NodeJS.Timeout> = new WeakMap()

  window.addEventListener(
    "click",
    (e) => {
      const target = e.target as HTMLElement

      const el = target.closest('div[class*="language-"] > button.copy') as HTMLElement | null
      if (!el) return

      const parent = el.parentElement
      const sibling = el.nextElementSibling?.nextElementSibling // <pre> tag
      if (!parent || !sibling) {
        return
      }

      e.stopImmediatePropagation?.()
      e.stopPropagation?.()

      const isShell = shellRE.test(parent.className)

      // Clone the node and remove the ignored nodes
      const clone = sibling.cloneNode(true) as HTMLElement
      clone.querySelectorAll(ignoredNodes).forEach((node) => node.remove())
      // remove extra newlines left after removing ignored nodes (affecting textContent because it is inside `<pre>`)
      // doesn't affect the newlines already in the code because they are rendered as `\n<span class="line"></span>`
      clone.innerHTML = clone.innerHTML.replace(/\n+/g, "\n")

      let text = clone.textContent || ""

      if (isShell) {
        text = text.replace(/^ *(\$|>) /gm, "").trim()
      }

      copyToClipboard(text).then(() => {
        el.classList.add("copied")
        window.$message?.success("复制成功")
        clearTimeout(timeoutIdMap.get(el))
        const timeoutId = setTimeout(() => {
          el.classList.remove("copied")
          el.blur()
          timeoutIdMap.delete(el)
        }, 2000)
        timeoutIdMap.set(el, timeoutId)
      })
    },
    { capture: true, passive: false }
  )
}

async function copyToClipboard(text: string) {
  try {
    return navigator.clipboard.writeText(text)
  } catch {
    const element = document.createElement("textarea")
    const previouslyFocusedElement = document.activeElement

    element.value = text

    // Prevent keyboard from showing on mobile
    element.setAttribute("readonly", "")

    element.style.contain = "strict"
    element.style.position = "absolute"
    element.style.left = "-9999px"
    element.style.fontSize = "12pt" // Prevent zooming on iOS

    const selection = document.getSelection()
    const originalRange = selection ? selection.rangeCount > 0 && selection.getRangeAt(0) : null

    document.body.appendChild(element)
    element.select()

    // Explicit selection workaround for iOS
    element.selectionStart = 0
    element.selectionEnd = text.length

    document.execCommand("copy")
    document.body.removeChild(element)

    if (originalRange) {
      selection?.removeAllRanges() // originalRange can't be truthy when selection is falsy
      selection?.addRange(originalRange)
    }

    // Get the focus back on the previously focused element, if any
    if (previouslyFocusedElement) {
      ;(previouslyFocusedElement as HTMLElement).focus()
    }
  }
}
